package com.platform.page;

import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.List;
import java.util.Properties;
import org.apache.ibatis.executor.parameter.ParameterHandler;
import org.apache.ibatis.executor.resultset.ResultSetHandler;
import org.apache.ibatis.executor.statement.StatementHandler;
import org.apache.ibatis.mapping.BoundSql;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.plugin.Interceptor;
import org.apache.ibatis.plugin.Intercepts;
import org.apache.ibatis.plugin.Invocation;
import org.apache.ibatis.plugin.Plugin;
import org.apache.ibatis.plugin.Signature;
import org.apache.ibatis.reflection.MetaObject;
import org.apache.ibatis.reflection.SystemMetaObject;
import org.apache.ibatis.scripting.defaults.DefaultParameterHandler;
import org.apache.log4j.Logger;

/**
 * Mybatis - 通用分页拦截器
 *
 * @author lipengjun
 * @email 939961241@qq.com
 * @date 2017年11月16日 下午10:43:36
 */
@Intercepts({@Signature(type = StatementHandler.class, method = "prepare", args = {Connection.class,
    Integer.class}),
    @Signature(type = ResultSetHandler.class, method = "handleResultSets", args = {
        Statement.class})})
public class PageHelper implements Interceptor {

  private static final Logger logger = Logger.getLogger(PageHelper.class);

  public static final ThreadLocal<Page> localPage = new ThreadLocal<Page>();

  /**
   * 开始分页
   */
  public static void startPage(int pageNum, int pageSize) {
    localPage.set(new Page(pageNum, pageSize));
  }

  /**
   * 结束分页并返回结果，该方法必须被调用，否则localPage会一直保存下去，直到下一次startPage
   */
  public static Page endPage() {
    Page page = localPage.get();
    localPage.remove();
    return page;
  }

  @Override
  public Object intercept(Invocation invocation) throws Throwable {
    if (localPage.get() == null) {
      return invocation.proceed();
    }
    if (invocation.getTarget() instanceof StatementHandler) {
      StatementHandler statementHandler = (StatementHandler) invocation.getTarget();
      MetaObject metaStatementHandler = SystemMetaObject.forObject(statementHandler);
      // 分离代理对象链(由于目标类可能被多个拦截器拦截，从而形成多次代理，通过下面的两次循环
      // 可以分离出最原始的的目标类)
      while (metaStatementHandler.hasGetter("h")) {
        Object object = metaStatementHandler.getValue("h");
        metaStatementHandler = SystemMetaObject.forObject(object);
      }
      // 分离最后一个代理对象的目标类
      while (metaStatementHandler.hasGetter("target")) {
        Object object = metaStatementHandler.getValue("target");
        metaStatementHandler = SystemMetaObject.forObject(object);
      }
      MappedStatement mappedStatement = (MappedStatement) metaStatementHandler
          .getValue("delegate.mappedStatement");
      //分页信息if (localPage.get() != null) {
      Page page = localPage.get();
      BoundSql boundSql = (BoundSql) metaStatementHandler.getValue("delegate.boundSql");
      // 分页参数作为参数对象parameterObject的一个属性
      String sql = boundSql.getSql();
      // 重写sql
      String pageSql = buildPageSqlMysql(sql, page);
      //重写分页sql
      metaStatementHandler.setValue("delegate.boundSql.sql", pageSql);
      Connection connection = (Connection) invocation.getArgs()[0];
      // 重设分页参数里的总页数等
      setPageParameter(sql, connection, mappedStatement, boundSql, page);
      // 将执行权交给下一个拦截器
      return invocation.proceed();
    } else if (invocation.getTarget() instanceof ResultSetHandler) {
      Object result = invocation.proceed();
      Page page = localPage.get();
      page.setResult((List) result);
      return result;
    }
    return null;
  }

  /**
   * 只拦截这两种类型的
   * <br>StatementHandler
   * <br>ResultSetHandler
   */
  @Override
  public Object plugin(Object target) {
    if (target instanceof StatementHandler || target instanceof ResultSetHandler) {
      return Plugin.wrap(target, this);
    } else {
      return target;
    }
  }

  @Override
  public void setProperties(Properties properties) {

  }


  /**
   * 修改原SQL为分页SQL orcal
   */
  private String buildPageSqlOrcal(String sql, Page page) {
    StringBuilder pageSql = new StringBuilder(200);
    pageSql.append("select * from ( select temp.*, rownum row_id from ( ");
    pageSql.append(sql);
    pageSql.append(" ) temp where rownum <= ").append(page.getEndRow());
    pageSql.append(") where row_id > ").append(page.getStartRow());
    return pageSql.toString();
  }

  /**
   * 修改原SQL为分页SQL mysql
   */
  private String buildPageSqlMysql(String sql, Page page) {
    StringBuilder pageSql = new StringBuilder(200);
    pageSql.append(sql);
    pageSql.append(" LIMIT ").append(page.getStartRow()).append(",")
        .append(page.getEndRow() - page.getStartRow());
    return pageSql.toString();
  }

  /**
   * 获取总记录数
   */
  private void setPageParameter(String sql, Connection connection, MappedStatement mappedStatement,
      BoundSql boundSql, Page page) {
    // 记录总记录数
    String countSql = "select count(0) from (" + sql + ")as total";
    PreparedStatement countStmt = null;
    ResultSet rs = null;
    try {
      countStmt = connection.prepareStatement(countSql);
      BoundSql countBS = new BoundSql(mappedStatement.getConfiguration(), countSql,
          boundSql.getParameterMappings(), boundSql.getParameterObject());
      setParameters(countStmt, mappedStatement, countBS, boundSql.getParameterObject());
      rs = countStmt.executeQuery();
      int totalCount = 0;
      if (rs.next()) {
        totalCount = rs.getInt(1);
      }
      page.setTotal(totalCount);
      int totalPage =
          totalCount / page.getPageSize() + ((totalCount % page.getPageSize() == 0) ? 0 : 1);
      page.setPages(totalPage);
    } catch (SQLException e) {
      logger.error("Ignore this exception", e);
    } finally {
      try {
        if (rs != null) {
          rs.close();
        }
      } catch (SQLException e) {
        logger.error("Ignore this exception", e);
      }
      try {
        countStmt.close();
      } catch (SQLException e) {
        logger.error("Ignore this exception", e);
      }
    }
  }

  /**
   * 代入参数值
   */
  private void setParameters(PreparedStatement ps, MappedStatement mappedStatement,
      BoundSql boundSql,
      Object parameterObject) throws SQLException {
    ParameterHandler parameterHandler = new DefaultParameterHandler(mappedStatement,
        parameterObject, boundSql);
    parameterHandler.setParameters(ps);
  }
}  